package com.baidu.shop.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.baidu.shop.base.BaseApiService;
import com.baidu.shop.base.Result;
import com.baidu.shop.document.GoodsDoc;
import com.baidu.shop.dto.SkuDTO;
import com.baidu.shop.dto.SpecParamDTO;
import com.baidu.shop.dto.SpuDTO;
import com.baidu.shop.entity.BrandEntity;
import com.baidu.shop.entity.CategoryEntity;
import com.baidu.shop.entity.SpecParamEntity;
import com.baidu.shop.entity.SpuDetailEntity;
import com.baidu.shop.feign.BrandFeign;
import com.baidu.shop.feign.CategoryFeign;
import com.baidu.shop.feign.GoodsFeign;
import com.baidu.shop.feign.SpecFeign;
import com.baidu.shop.response.GoodsResponse;
import com.baidu.shop.service.ShopElasticsearchService;
import com.baidu.shop.utils.ESHighLightUtil;
import com.baidu.shop.utils.JSONUtil;
import com.baidu.shop.utils.StringUtil;
import com.github.pagehelper.PageInfo;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.http.HttpStatus;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RestController;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Description:
 * @ClassName: ShopElasticsearchServiceImpl
 * @Author: User
 * @date: 2020.09.16 20:56
 * @version: 1.0
 */
@RestController
public class ShopElasticsearchServiceImpl  extends BaseApiService implements ShopElasticsearchService {
    @Autowired
    private GoodsFeign goodsFeign;

    @Autowired
    private SpecFeign specFeign;

    @Autowired
    private BrandFeign brandFeign;

    @Autowired
    private CategoryFeign categoryFeign;

    @Autowired
    private ElasticsearchRestTemplate template;


    @Override
    public Result<JSONObject> deleteSearchById(String id) {
        template.delete(id,GoodsDoc.class);
        return this.setResultSuccess();
    }

    @Override
    public Result<List<GoodsDoc>> search(String search, Integer pages,String filter) {
        //查询
        SearchHits<GoodsDoc> searchHits = template.search(getNativeSearchQueryBuilder(search, pages, filter), GoodsDoc.class);
        //获取查询结果
        List<GoodsDoc> GoodsDocs = ESHighLightUtil.getHighLightHit(searchHits.toList());
        //获取总条数和页数
        Long total = searchHits.getTotalHits();
        Long pageTotal =Double.valueOf(Math.ceil((total.doubleValue() / 10))).longValue();
        //获取分类和品牌聚合
        Aggregations aggregations = searchHits.getAggregations();
        //获取品牌信息
        List<BrandEntity> brandByIdList = getBrandByIdList(aggregations);

        //获取分类信息获取规格参数
        Map<List<Long>, List<CategoryEntity>> categoryById = getCategoryByIdList(aggregations);
        List<CategoryEntity> categoryByIdList=null;
        Map<String, List<String>> specMap=null;
        for (Map.Entry<List<Long>, List<CategoryEntity>> listListEntry : categoryById.entrySet()) {
            categoryByIdList=listListEntry.getValue();
            specMap = getSpecMap(listListEntry.getKey().get(0).intValue(), search);
        }
        //将信息放入GoodsResponse中
        GoodsResponse goodsResponse = new GoodsResponse(total.intValue(), pageTotal.intValue(),
                brandByIdList, categoryByIdList, GoodsDocs,specMap);
        return goodsResponse;
    }

    private Map<String,List<String>> getSpecMap(Integer cid,String search) {
        SpecParamDTO specParamDTO = new SpecParamDTO();
        specParamDTO.setCid(cid);
        specParamDTO.setSearching(true);
        Map<String,List<String>> specMap=new HashMap<>();
        Result<List<SpecParamEntity>> specParamInfo = specFeign.getSpecParamInfo(specParamDTO);
        if (specParamInfo.getCode()==200) {
            List<SpecParamEntity> SpecParamList = specParamInfo.getData();
            NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
            if (!StringUtils.isEmpty(search)) queryBuilder.withQuery(QueryBuilders.multiMatchQuery(search, "title", "brandName", "categoryName"));
            SpecParamList.forEach(spec -> {
                queryBuilder.addAggregation(
                        AggregationBuilders
                                .terms(spec.getName())
                                .field("specs." + spec.getName() + ".keyword"));
            });
            queryBuilder.withPageable(PageRequest.of(0, 1));
            Aggregations aggregations = template.search(queryBuilder.build(), GoodsDoc.class).getAggregations();

            SpecParamList.forEach(spec -> {
                Terms aggregation = aggregations.get(spec.getName());
                List<? extends Terms.Bucket> buckets = aggregation.getBuckets();
                List<String> specparam = buckets.stream().map(bucket -> {
                    return bucket.getKeyAsString();
                }).collect(Collectors.toList());

                specMap.put(spec.getName(), specparam);
            });
            return specMap;
        }
        return  null;
    }


    private Map<List<Long>,List<CategoryEntity>> getCategoryByIdList(Aggregations aggregations) {
        Terms categoryTerms = aggregations.get("category_add");

        List<Long> maxDocCount = Arrays.asList(0L);
        List<Long> maxCategoryId = Arrays.asList(0L);
        Map<List<Long>,List<CategoryEntity>> getCategoryByIdList = new HashMap<>();
        //根据聚合出的分类id查询分类相关信息
        List<? extends Terms.Bucket> categorybuckets = categoryTerms.getBuckets();
        List<String> categoryIds = categorybuckets.stream().map(categorybucket -> {
            long docCount = categorybucket.getDocCount();
           if (docCount > maxDocCount.get(0)){
                maxDocCount.set(0, docCount);
                maxCategoryId.set(0, categorybucket.getKeyAsNumber().longValue());
           }
            return categorybucket.getKeyAsString();
        }).collect(Collectors.toList());
        Result<List<CategoryEntity>> categoryByIdList = categoryFeign.getCategoryByIdList(String.join(",", categoryIds));

        //将热度最高的放在集合首位
        int hotKey = 0;
        for (int i = 0; i < categoryByIdList.getData().size(); i++) {
            if (categoryByIdList.getData().get(i).getId()==maxCategoryId.get(0).intValue()){
                hotKey=i;
            }
        }
        Collections.swap(categoryByIdList.getData(),hotKey,0);

        getCategoryByIdList.put(maxCategoryId, categoryByIdList.getData());
        return getCategoryByIdList;
    }


    private  List<BrandEntity> getBrandByIdList(Aggregations aggregations) {
        Terms brandTerms = aggregations.get("brand_add");

        List<Long> maxDocCount = Arrays.asList(0L);
        List<Long> maxBrandId = Arrays.asList(0L);
        //根据聚合出的品牌id查询品牌相关信息
        List<? extends Terms.Bucket> brandbuckets = brandTerms.getBuckets();
        List<String> brandIds= brandbuckets.stream().map(brandbucket -> {
            long docCount = brandbucket.getDocCount();
            if (docCount > maxDocCount.get(0)){
                maxDocCount.set(0, docCount);
                maxBrandId.set(0, brandbucket.getKeyAsNumber().longValue());
            }
            return brandbucket.getKeyAsString();
        }).collect(Collectors.toList());

        Result<List<BrandEntity>> brandByIdList = brandFeign.getBrandByIdList(String.join(",", brandIds));

        //将热度最高的放在集合首位
        int hotKey = 0;
        for (int i = 0; i < brandByIdList.getData().size(); i++) {
            if (brandByIdList.getData().get(i).getId()==maxBrandId.get(0).intValue()){
                hotKey=i;
            }
        }
        Collections.swap(brandByIdList.getData(),hotKey,0);

        return brandByIdList.getData();
    }

    private NativeSearchQuery getNativeSearchQueryBuilder(String search, Integer pages ,String filter) {
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        //获取过滤数据
        if(StringUtil.isNotEmpty(filter) && filter.length()>2){
            Map<String, String> filterMap = JSONUtil.toMapValueString(filter);
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            filterMap.forEach((key,value)->{
                MatchQueryBuilder matchQueryBuilder = null;
                if (key.equals("brandId") || key.equals("cid3")){
                    matchQueryBuilder =  QueryBuilders.matchQuery(key, value);
                }else{
                    matchQueryBuilder =  QueryBuilders.matchQuery("specs." + key + ".keyword", value);
                }
                boolQueryBuilder.must(matchQueryBuilder);
            });
            queryBuilder.withFilter(boolQueryBuilder);
        }
        if (!StringUtils.isEmpty(search)) queryBuilder.withQuery(QueryBuilders.multiMatchQuery(search, "title","brandName","categoryName"));
        queryBuilder.withPageable(PageRequest.of(pages -1, 10));

        queryBuilder.addAggregation(AggregationBuilders.terms("category_add").field("cid3"));
        queryBuilder.addAggregation(AggregationBuilders.terms("brand_add").field("brandId").size(20));

        queryBuilder.withHighlightBuilder(ESHighLightUtil.getHighlightBuilder("title"));
        return queryBuilder.build();
    }

    @Override
    public Result<JSONObject> initGoodsEsData(Integer spuId) {
        IndexOperations indexOperations = template.indexOps(GoodsDoc.class);
        if (!indexOperations.exists()) {
            indexOperations.createMapping();
        }
        List<GoodsDoc> goodsDocs = this.esGoodsInfo(spuId);
        template.save(goodsDocs);
        return this.setResultSuccess();

    }


    @Override
    public Result<JSONObject> clearGoodsEsData() {
        IndexOperations indexOperations = template.indexOps(GoodsDoc.class);
        if (indexOperations.exists()) {
            indexOperations.delete();
        }
        return this.setResultSuccess();
    }

    public  List<GoodsDoc> esGoodsInfo(Integer spuId) {
        SpuDTO spuDTO = new SpuDTO();
        if (spuId!=null){
            spuDTO.setId(spuId);
        }
        Result<PageInfo<SpuDTO>> spuInfo = goodsFeign.getSpuInfo(spuDTO);

        List<GoodsDoc> GoodsDocs =null;
        if (spuInfo.getCode() == HttpStatus.SC_OK){
            //获取spu集合
            PageInfo<SpuDTO> data = spuInfo.getData();
            List<SpuDTO> list = data.getList();

            //遍历所有的spuDto查询相关数据
            GoodsDocs = list.stream().map(spu -> {
                GoodsDoc goodsDoc = getGoodsDoc(spu);
                return goodsDoc;
            }).collect(Collectors.toList());

        }

        return GoodsDocs;
    }

    /**
     * 根据spu 查询相关数据封装到GoodsDoc
     * @param spu
     * @return GoodsDoc
     */
    private GoodsDoc getGoodsDoc(SpuDTO spu){
        Integer spuId = spu.getId();
        //创建文档实体
        GoodsDoc goodsDoc = new GoodsDoc();
        //spu信息填充
        goodsDoc.setId(spuId.longValue());
        goodsDoc.setCid1(spu.getCid1().longValue());
        goodsDoc.setCid2(spu.getCid2().longValue());
        goodsDoc.setCid3(spu.getCid3().longValue());
        goodsDoc.setCreateTime(spu.getCreateTime());
        goodsDoc.setSubTitle(spu.getSubTitle());
        goodsDoc.setBrandId(spu.getBrandId().longValue());
        //可搜索数据
        goodsDoc.setTitle(spu.getTitle());
        goodsDoc.setBrandName(spu.getBrandName());
        goodsDoc.setCategoryName(spu.getCategoryName());

        //sku信息填充 获取sku
        Map<List<Long>, List<Map<String, Object>>> skuAndPriceList = getSkuAndPriceList(spuId);
        skuAndPriceList.forEach((key,value)->{
            goodsDoc.setPrice(key);
            goodsDoc.setSkus(JSONUtil.toJsonString(value));
        });

        //填充规格参数
        Map<String, Object> specMap = getSpecMap(spu);
        goodsDoc.setSpecs(specMap);

        return  goodsDoc;
    }

    private Map<String, Object> getSpecMap(SpuDTO spu){
        //根据spu的分类id来获取规格参数
        SpecParamDTO specParamDTO = new SpecParamDTO();
        specParamDTO.setCid(spu.getCid3());
        Result<List<SpecParamEntity>> specParamInfo = specFeign.getSpecParamInfo(specParamDTO);
        //规格参数和spu one to one 的关系

        //定义specMap来接值
        Map<String, Object> specMap = new HashMap<>();
        if (specParamInfo.getCode() == HttpStatus.SC_OK) {

            //获取spec
            List<SpecParamEntity> specList = specParamInfo.getData();
            //通过spuid获取spuDetil
            Result<SpuDetailEntity> spuDetail = goodsFeign.getSpuDetailBydSpu(spu.getId());

            if (spuDetail.getCode() == HttpStatus.SC_OK) {
                //获取规格参数
                SpuDetailEntity spuDetailEntity = spuDetail.getData();

                //获取通用规格参数
                String genericSpec = spuDetailEntity.getGenericSpec();
                Map<String, String> genericSpecMap = JSONUtil.toMapValueString(genericSpec);

                //获取特有规格参数
                String specialSpec = spuDetailEntity.getSpecialSpec();
                Map<String, List<String>> specialSpecMap = JSONUtil.toMapValueStrList(specialSpec);

                //遍历参数列表 根据参数的id来获取规格参数
                specList.stream().forEach(spec -> {
                    if (spec.getGeneric()) {//是否是通用属性 ,填充通用属性
                        if (spec.getNumeric() && spec.getSearching()) {
                            specMap.put(spec.getName(), this.chooseSegment(genericSpecMap.get(spec.getId() + ""), spec.getSegments(), spec.getUnit()));
                        } else {
                            specMap.put(spec.getName(), genericSpecMap.get(spec.getId() + ""));
                        }
                    } else {//填充特殊属性
                        specMap.put(spec.getName(), specialSpecMap.get(spec.getId() + ""));
                    }
                });
            }
        }
        return specMap;
    }

    private Map<List<Long>, List<Map<String, Object>> > getSkuAndPriceList(Integer spuId){
        Map<List<Long>, List<Map<String, Object>>> listListHashMap = new HashMap<>();
        Result<List<SkuDTO>> skuBySpuId = goodsFeign.getSkuBySpuId(spuId);
        List<SkuDTO> skuDTOList = skuBySpuId.getData();
        //定义pricelist集合
        List<Long> priceList = new ArrayList<>();
        //定义sku集合
        List<Map<String, Object>> skuMapList = null;
        if (skuBySpuId.getCode() == HttpStatus.SC_OK) {
            skuMapList = skuDTOList.stream().map(skuDTO -> {
                //填充sku
                Map<String, Object> skuMap = new HashMap<>();
                skuMap.put("id", skuDTO.getId());
                skuMap.put("title", skuDTO.getTitle());
                skuMap.put("images", skuDTO.getImages());
                skuMap.put("price", skuDTO.getPrice());
                //填充价格
                priceList.add(skuDTO.getPrice().longValue());
                return skuMap;
            }).collect(Collectors.toList());
        }
        listListHashMap.put(priceList,skuMapList);
        return listListHashMap;
    }
    /**
     * 把具体的值转换成区间-->不做范围查询
     * @param value
     * @param segments
     * @param unit
     * @return
     */
    private String chooseSegment(String value, String segments, String unit) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存数值段
        for (String segment : segments.split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判断是否在范围内
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + unit + "以上";
                }else if(begin == 0){
                    result = segs[1] + unit + "以下";
                }else{
                    result = segment + unit;
                }
                break;
            }
        }
        return result;
    }
}
